#pragma once

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <sys/shm.h>

// 定义共享内存空间的大小（4096的整数倍)
const int gsize = 128;

// 用户指明的随机路径与ID（生成随机key）
#define PATHNAME "/tmp"
#define PROJ_ID 0x66

class Shm
{
public:
    Shm(int size = gsize)
        : _shmid(-1), _size(size), _start_addr(nullptr)
    {
    }

    // 清除共享内存函数
    void Delete()
    {
        int n = shmctl(_shmid, IPC_RMID, nullptr);
        (void)n;
    }

    // 挂起共享内存函数
    void Attach()
    {
        _start_addr = shmat(_shmid, nullptr, 0);
        if ((long long int)_start_addr == -1)
            exit(3);
    }

    // 打印共享内存结构体内信息函数
    void PrintAttr()
    {
        struct shmid_ds ds;
        int n = shmctl(_shmid, IPC_STAT, &ds);
        if (n < 0)
        {
            perror("shmctl");
            exit(4);
        }
        printf("key: 0x%x\n", ds.shm_perm.__key);
        printf("shm_nattch: %ld\n", ds.shm_nattch);
        printf("shm_segsz: 0x%lx\n", ds.shm_segsz);
    }

    // 分离共享内存函数
    void Detach()
    {
        int n = shmdt(_start_addr);
        (void)n;
    }

    // 创建共享内存函数
    void Create()
    {
        GetHelper(IPC_CREAT | IPC_EXCL | 0666);
    }

    // 获取共享内存函数
    void Get()
    {
        GetHelper(IPC_CREAT);
    }

    // 获取共享内存空间映射首地址函数
    void *Addr()
    {
        return _start_addr;
    }

    // 获取空间大小函数
    int Size()
    {
        return _size;
    }

    ~Shm() {}

private:
    // 获取随机键值函数
    key_t GetKey()
    {
        return ftok(PATHNAME, PROJ_ID);
    }

    // 对共享内存创建与获取的操作函数
    void GetHelper(int shmflg)
    {
        // 1.构建键值
        key_t k = GetKey();
        if (k < 0)
        {
            std::cerr << "GetKey error" << std::endl;
            exit(1);
        }
        // 2.创建新的共享内存
        _shmid = shmget(k, _size, shmflg);
        if (_shmid < 0)
        {
            perror("shmget");
            exit(2);
        }
        printf("key = 0x%x,_shmid = %d\n", k, _shmid);
    }

private:
    // 共享内存ID（标识符）
    int _shmid;
    // 内存大小
    int _size;
    // 共享内存在虚拟地址空间映射的起始地址
    void *_start_addr;
};